/*
 * Copyright (c) 2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.libermundi.theorcs.security.tapestry.services;

import java.io.File;
import java.io.IOException;

import org.apache.tapestry5.Link;
import org.apache.tapestry5.SymbolConstants;
import org.apache.tapestry5.alerts.AlertManager;
import org.apache.tapestry5.internal.services.AssetResourceLocator;
import org.apache.tapestry5.internal.services.RequestPageCache;
import org.apache.tapestry5.ioc.Configuration;
import org.apache.tapestry5.ioc.MappedConfiguration;
import org.apache.tapestry5.ioc.Messages;
import org.apache.tapestry5.ioc.OrderedConfiguration;
import org.apache.tapestry5.ioc.Resource;
import org.apache.tapestry5.ioc.ServiceBinder;
import org.apache.tapestry5.ioc.annotations.Contribute;
import org.apache.tapestry5.ioc.annotations.Decorate;
import org.apache.tapestry5.ioc.annotations.Local;
import org.apache.tapestry5.ioc.annotations.Order;
import org.apache.tapestry5.ioc.annotations.Symbol;
import org.apache.tapestry5.ioc.annotations.Value;
import org.apache.tapestry5.ioc.services.ServiceOverride;
import org.apache.tapestry5.services.ApplicationInitializerFilter;
import org.apache.tapestry5.services.ApplicationStateContribution;
import org.apache.tapestry5.services.ApplicationStateCreator;
import org.apache.tapestry5.services.AssetFactory;
import org.apache.tapestry5.services.BaseURLSource;
import org.apache.tapestry5.services.ComponentClassResolver;
import org.apache.tapestry5.services.LibraryMapping;
import org.apache.tapestry5.services.RequestExceptionHandler;
import org.apache.tapestry5.services.RequestGlobals;
import org.apache.tapestry5.services.Response;
import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
import org.apache.tapestry5.upload.services.UploadSymbols;
import org.libermundi.tapestry.elfinder.services.FileType;
import org.libermundi.tapestry.elfinder.services.Volume;
import org.libermundi.theorcs.core.model.Email;
import org.libermundi.theorcs.core.tapestry.services.AppHelper;
import org.libermundi.theorcs.core.tapestry.services.FreeMarkerService;
import org.libermundi.theorcs.core.tapestry.services.assets.FileValidator;
import org.libermundi.theorcs.core.tapestry.services.assets.ImageCache;
import org.libermundi.theorcs.core.tapestry.services.assets.OfsFilesUtils;
import org.libermundi.theorcs.core.tapestry.services.assets.OrcsFileSystem;
import org.libermundi.theorcs.core.tapestry.services.configuration.ApplicationConfig;
import org.libermundi.theorcs.core.util.OrcsHome;
import org.libermundi.theorcs.security.SecurityConstants;
import org.libermundi.theorcs.security.model.User;
import org.libermundi.theorcs.security.services.SecurityManager;
import org.libermundi.theorcs.security.services.UserManager;
import org.libermundi.theorcs.security.tapestry.internal.SecurityChecker;
import org.libermundi.theorcs.security.tapestry.internal.SpringSecurityWorker;
import org.libermundi.theorcs.security.tapestry.internal.StaticSecurityChecker;
import org.libermundi.theorcs.security.tapestry.model.emails.ValidationEmail;
import org.libermundi.theorcs.security.tapestry.services.impl.GigyaServicesImpl;
import org.libermundi.theorcs.security.tapestry.services.impl.OrcsUrlSourceImpl;
import org.libermundi.theorcs.security.tapestry.services.impl.OrcsVolume;
import org.libermundi.theorcs.security.tapestry.services.impl.SecurityHelperImpl;
import org.libermundi.theorcs.security.tapestry.services.impl.UserServicesImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationManager;

import com.google.common.base.Strings;

public class SecurityModule {
	public static void bind(ServiceBinder binder) {
        binder.bind(SecurityHelper.class, SecurityHelperImpl.class);
        binder.bind(UserServices.class,UserServicesImpl.class);
        binder.bind(GigyaServices.class,GigyaServicesImpl.class);
        binder.bind(BaseURLSource.class, OrcsUrlSourceImpl.class).withId("OrcsUrlSource");
     }
	
	public static void contributeComponentClassTransformWorker( OrderedConfiguration<ComponentClassTransformWorker2> configuration, SecurityChecker securityChecker) {
        configuration.add("SpringSecurity", new SpringSecurityWorker(securityChecker));
    }

    public static SecurityChecker buildSecurityChecker(
            final AccessDecisionManager accessDecisionManager,
            final AuthenticationManager authenticationManager)
            		throws Exception {
        StaticSecurityChecker checker = new StaticSecurityChecker();
        checker.setAccessDecisionManager(accessDecisionManager);
        checker.setAuthenticationManager(authenticationManager);
        checker.afterPropertiesSet();
        return checker;
    }
    
    public static void contributeApplicationDefaults(MappedConfiguration<String, String> configuration, ApplicationConfig appConfig) {
    	configuration.add(SymbolConstants.HMAC_PASSPHRASE, appConfig.getString(SecurityConstants.HMAC_PASSPHRASE));
    }

	public void contributeApplicationStateManager(MappedConfiguration<Class<?>, ApplicationStateContribution> configuration,
			final UserManager userManager) {
		//TODO: A virer ?
		ApplicationStateCreator<User> userCreator = new ApplicationStateCreator<User>() {
			@Override
			public User create() {
				return userManager.getUser();
			}
		};

		configuration.add(User.class, new ApplicationStateContribution("session", userCreator));
	
	}
	public static void contributeFactoryDefaults(MappedConfiguration<String, String> configuration) {
        configuration.add("security.assets", "classpath:org/libermundi/theorcs/security/tapestry/assets");

        configuration.add("security.scripts", "${security.assets}/js");
        configuration.add("security.styles", "${security.assets}/css");
        configuration.add("security.images", "${security.assets}/images");

    }
	
	public void contributeApplicationInitializer(OrderedConfiguration<ApplicationInitializerFilter> configuration) {
	        configuration.add("securityModuleInitializer", new SecurityModuleInitializer(), "before:*");
	}
	
	public static void contributeComponentClassResolver(final Configuration< LibraryMapping > configuration) {
	        configuration.add(new LibraryMapping(SecurityConstants.TAPESTRY_MAPPING, "org.libermundi.theorcs.security.tapestry"));
	}
	
	public static void contributeComponentMessagesSource(
			@Value("/META-INF/lang/security.properties")
			Resource securityCatalogResource,
			OrderedConfiguration<Resource> configuration) {
			configuration.add("SecurityCatalog", securityCatalogResource, "before:AppCatalog");
	}
	
	public static void contributeFreemarkerEmailService(MappedConfiguration<String, Email> configuration,
			final ApplicationConfig appConfig,
			final FreeMarkerService freeMarkerService,
			@OrcsFileSystem final AssetFactory assetFactory,
			final Messages messages) {
		configuration.add(SecurityConstants.EMAIL_VALIDATION_ID, new ValidationEmail(appConfig,freeMarkerService,assetFactory,messages));
	}
	
	@Contribute(ServiceOverride.class)
	public static void contributeServiceOverride(MappedConfiguration<Class<?>, Object> configuration, @Local BaseURLSource orcsSource){
		configuration.add(BaseURLSource.class, orcsSource);
	}
	
	public static void contributeApplicationConfiguration(
			OrderedConfiguration<String> configuration,
			OrcsHome theOrcsHome) {
		configuration.add("security-default", "classpath://META-INF/conf/security-default.properties","after:core-default");
		configuration.add("sns-default", "classpath://META-INF/conf/sns-default.properties","after:security-default");
		configuration.add("security-local", theOrcsHome.getPath() + "/conf/security.properties","after:security-default");
		configuration.add("sns-local", theOrcsHome.getPath() + "/conf/sns.properties","after:sns-default");
	}
	
	/*
	 * Remove the Csrf Protection from the Activation Page
	 */
	public static void contributeCsrfProtectedPages(Configuration<String> pages){
		pages.add("security/Activation");
	}
	
	/*
	 * ElFinder
	 */
	public static Volume buildUserVolume(final OrcsHome orcsHomeDir, @OrcsFileSystem AssetFactory assetFactory,
			@OrcsFileSystem AssetResourceLocator resourceLocator, SecurityManager securityManager,
			FileValidator fileValidator, ImageCache imageCache,@Symbol(UploadSymbols.FILESIZE_MAX) long maxFileSize){
		String basePath = OfsFilesUtils.computerBaseFilerPath(orcsHomeDir.getPath(), "fm");
		String baseUrl;
		try {
			Resource r = resourceLocator.findClasspathResourceForPath("fm");
			baseUrl = assetFactory.createAsset(r).toClientURL();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		
		Volume userVolume = new OrcsVolume("uservolume", new File(basePath), baseUrl, securityManager, imageCache, maxFileSize);
		
		userVolume.allowExtension(FileType.IMAGE, fileValidator.getExtensions(org.libermundi.theorcs.core.tapestry.services.assets.FileType.IMAGE) );
		userVolume.allowExtension(FileType.DOCUMENT, fileValidator.getExtensions(org.libermundi.theorcs.core.tapestry.services.assets.FileType.DOCUMENT) );
		userVolume.allowExtension(FileType.SOUND, fileValidator.getExtensions(org.libermundi.theorcs.core.tapestry.services.assets.FileType.SOUND) );
		userVolume.allowExtension(FileType.VIDEO, fileValidator.getExtensions(org.libermundi.theorcs.core.tapestry.services.assets.FileType.VIDEO) );
		
		return userVolume;
	}
	
	public static void contributeVolumeSource(OrderedConfiguration<Volume> configuration, @Local Volume userVolume) {
		configuration.add("userVolume",userVolume);
	}
	
	// handle AccessDeniedException
	@Decorate(serviceInterface=RequestExceptionHandler.class,id="accessDeniedExceptionHandler")
	@Order("after:redirectExceptionHandler")
    public static RequestExceptionHandler decorateRequestExceptionHandler(
    				final RequestExceptionHandler delegate,
    				final Response response,
    				final AppHelper appHelper,
    				final RequestPageCache requestPageCache,
    				final ComponentClassResolver resolver,
    				final ApplicationConfig appConfig,
    				final AlertManager alertManager,
    				final Messages messages,
    				final RequestGlobals request) {
        return new RequestExceptionHandler() {
        	private final Logger logger = LoggerFactory.getLogger(RequestExceptionHandler.class);
            @Override
			public void handleRequestException(Throwable exception) throws IOException {
            	if(logger.isDebugEnabled()) {
            		logger.debug("---------- AccessDeniedException RequestExceptionHandler Decorator ----------");
            	}
                // check if wrapped
                Throwable cause = exception;
                if (exception.getCause() instanceof AccessDeniedException) {
                    cause = exception.getCause();
                }

                //Better way to check if the cause is AccessDeniedException. Sometimes it's wrapped pretty deep..
                int i = 0;
                while(true){
                    if(cause == null || cause instanceof AccessDeniedException || i > 1000){
                        break;
                    }
                    i++;
                    cause = cause.getCause();
                }

                // check for redirect
                if (cause instanceof AccessDeniedException) {
                	StringBuffer requestedUrl = null;
                	if(!request.getRequest().isXHR()){
                		requestedUrl = request.getHTTPServletRequest().getRequestURL();
                		if(!Strings.isNullOrEmpty(request.getHTTPServletRequest().getQueryString())){
                			requestedUrl.append("?")
                				.append(request.getHTTPServletRequest().getQueryString());
                		}
                	}
                	
                    // check for class and string
                	String accessDeniedUrl = appConfig.getString("security.access-denied-url");
                	if(accessDeniedUrl.startsWith("/")) {
                		accessDeniedUrl = accessDeniedUrl.substring(1);
                	}
                    Link pageLink = appHelper.getPageLink(accessDeniedUrl,requestedUrl);
                    
                    if (pageLink == null) {
                    	//Worst case scenario we redirect to Index Page
                    	pageLink = appHelper.getPageLink(appConfig.getString("core.pages.home"),requestedUrl);
                    }

                    alertManager.error(messages.get("security.access-denied"));
                    
                	String redirectURI = pageLink.toRedirectURI();
                	logger.warn("Catched an AccessDeniedException. Redirecting to : " + redirectURI);

                    response.sendRedirect(redirectURI);
                    return;
                }

                // no redirect so pass on the exception
                delegate.handleRequestException(exception);
            }
            
       };
        
    }
}
